#pragma once

#include <iostream>
#include <sstream>
#include <string>
#include <algorithm>
#include <vector>
#include <unordered_map>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/sendfile.h>
#include <cstdlib>
#include "Util.hpp"
#include "Log.hpp"

#define SEP ": "
#define WEB_ROOT "wwwroot"
#define HOME_PAGE "index.html"
#define HTTP_VERSION "HTTP/1.0"
#define LINE_END "\r\n"

#define OK 200
#define BAD_REQUEST 400
#define NOT_FOUND 404
#define INTERNAL_SERVER_ERROR 500

#define PAGE_400 "400.html"
#define PAGE_404 "404.html"
#define PAGE_500 "500.html"


// 根据状态码获取状态码描述
static std::string CodeToDesc(int code)
{
    std::string desc;
    switch (code)
    {
    case 200:
        desc = "OK";
        break;
    case 400:
        desc = "Bad Request";
        break;
    case 404:
        desc = "Not Found";
        break;
    case 500:
        desc = "Internal Server Error";
        break;
    default:
        break;
    }
    return desc;
}

// HTTP请求
class HttpRequest
{
public:
    // HTTP请求内容
    std::string _request_line;                // 请求行
    std::vector<std::string> _request_header; // 请求报头
    std::string _blank;                       // 空行--分割正文部分
    std::string _request_body;                // 正文部分

    // 解析内容--后续需要使用到的属性
    std::string _method;                                     // 请求方法
    std::string _uri;                                        // URI
    std::unordered_map<std::string, std::string> _header_kv; // 请求报头中的键值对
    int _content_length;                                     // 正文长度
    std::string _path;                                       // 请求资源路径
    std::string _query_string;                               // URI中携带的参数
    std::string _version;                                    // HTTP版本

    // CGI相关
    bool _cgi; // 是否使用CGI模式
public:
    HttpRequest()
        : _content_length(0), _cgi(false)
    {
    }

    ~HttpRequest() {}
};

// HTTP响应
class HttpResponse
{
public:
    // HTTP响应内容（返回给客户端的内容）
    std::string _status_line;                  // 状态行
    std::vector<std::string> _response_header; // 响应报头
    std::string _blank;                        // 空行
    std::string _response_body;                // 响应正文（CGI相关）

    // 构建响应内容需要使用的数据
    int _status_code;    // 由响应码得到响应描述，构建状态行
    int _fd;             // 响应文件的fd （非CGI相关）
    int _size;           // 响应文件的size （非CGI相关）
    std::string _suffix; // 响应文件的后缀 （非CGI相关）
public:
    HttpResponse()
        : _blank(LINE_END),
          _status_code(OK),
          _fd(-1),
          _size(0)
    {
    }
    ~HttpResponse() {}
};

// 读取请求、分析请求、构建响应
// IO通信

// 服务端EndPoint
class EndPoint
{
private:
    int _sock;                   // 通信sockfd
    HttpRequest _http_request;   // HTTP请求
    HttpResponse _http_response; // HTTP响应
    bool _stop;                  // 是否停止本次操作
private:
    // 根据后缀获取资源类型
    static std::string SuffixToDesc(const std::string &suffix)
    {
        static std::unordered_map<std::string, std::string> suffix_to_desc = {
            {".html", "text/html"},
            {".css", "text/css"},
            {".js", "application/x-javascript"},
            {".jpg", "application/x-jpg"},
            {".xml", "text/xml"}};

        auto iter = suffix_to_desc.find(suffix);
        if (iter != suffix_to_desc.end())
        {
            return iter->second;
        }

        // 默认资源为html文件
        return "text/html";
    }

    // 读取请求行
    bool RecvHttpRequestLine()
    {
        auto &line = _http_request._request_line;
        std::cout << "-----请求行内容-----" << std::endl;
        if (Util::ReadLine(_sock, line) > 0)
        {
            line.resize(line.size() - 1); // 去掉读取上来的\n
            // line.pop_back();
        }
        else
        { // 读取出错，停止本次服务处理
            _stop = true;
            LOG(DEBUG, "RecvHttpRequestLine()读取请求行错误");
        }
        LOG(DEBUG, "RecvHttpRequestLine()执行完毕");
        return _stop;
    }

    // 读取请求报头和空行
    bool RecvHttpRequestHeader()
    {
        // 从sock中读取，按行读取，直到读取到报头与正文的分隔符
        std::string line;
        while (true)
        {
            line.clear();
            if (Util::ReadLine(_sock, line) > 0)
            {
                if (line == "\n")
                {
                    // 读取到了分隔符
                    _http_request._blank = line;
                    break;
                }
                else
                {
                    // 读取到报头
                    line.resize(line.size() - 1); // 去掉读取上来的\n
                    _http_request._request_header.push_back(line);
                }
            }
            else
            { // 读取出错，停止本次服务处理
                _stop = true;
                LOG(DEBUG, "RecvHttpRequestHeader()读取报头和空行出错");
                break;
            }
        }
        LOG(DEBUG, "RecvHttpRequestHeader()执行完毕");
        return _stop;
    }
    // 根据_http_request._request_line解析出请求方法、URI、协议版本
    void ParseHttpRequestLine()
    {
        auto &line = _http_request._request_line;
        // 目标属性在line中以空格分隔
        std::stringstream ss(line);
        ss >> _http_request._method >> _http_request._uri >> _http_request._version;

        // 将请求方法转换为大写
        auto &method = _http_request._method;
        std::transform(method.begin(), method.end(), method.begin(), toupper);
    }
    // 根据请求报头解析出键值对
    void ParseHttpRequestHeader()
    {
        std::string key;
        std::string value;
        for (auto &iter : _http_request._request_header)
        {
            if (Util::CutString(iter, key, value, SEP))
            {
                _http_request._header_kv.insert({key, value});
            }
        }
    }
    bool IsNeedRecvHttpRequestBody()
    {
        auto &method = _http_request._method;
        if (method == "POST")
        {
            // 如果需要读取正文需要得到正文长度
            auto &kv = _http_request._header_kv;
            auto iter = kv.find("Content-Length");
            if (iter != kv.end())
            {
                _http_request._content_length = std::atoi(iter->second.c_str());
                return true;
            }
        }

        return false;
    }
    // 读取请求正文
    bool RecvHttpRequestBody()
    {
        // 读取之前需要进行判断：只有POST请求需要读取正文部分
        if (IsNeedRecvHttpRequestBody())
        {
            // 这里采用一个一个字符读取的方式，以便检查出错误
            char ch = 0;
            auto &body = _http_request._request_body;
            int content_len = _http_request._content_length;
            while (content_len)
            {
                ssize_t n = recv(_sock, &ch, 1, 0);
                if (n > 0)
                {
                    body += ch;
                    --content_len;
                }
                else
                {
                    _stop = true; // 读取出错或对端关闭，停止过本次处理
                    break;
                }
            }
        }
        return _stop;
    }

    // CGI模式处理, 返回状态码
    int ProcessCGI()
    {
        int code = OK; // 默认状态码为200

        auto &method = _http_request._method; // 请求方法
        auto &bin = _http_request._path;      // 需要执行的应用程序路径

        // 获取执行程序所需要的参数
        auto &request_body = _http_request._request_body; // POST的参数在请求正文中
        int content_len = _http_request._content_length;  // 请求正文长度
        auto &query_string = _http_request._query_string; // GET的参数

        auto &response_body = _http_response._response_body; // 将结果存放在响应正文中

        // 创建通信管道(这里以服务器视角进行命名-父进程)
        int input[2];        // 读管道
        if (pipe(input) < 0) // 创建管道失败
        {
            LOG(ERROR, "pipe input error!");
            code = INTERNAL_SERVER_ERROR;
            return code;
        }
        int output[2];        // 写管道
        if (pipe(output) < 0) // 创建管道失败
        {
            LOG(ERROR, "pipe input error!");
            code = INTERNAL_SERVER_ERROR;
            return code;
        }

        // 创建子进程
        pid_t pid = fork();
        if (pid == 0)
        {
            // 子进程
            close(input[0]);
            close(output[1]);

            // 将请求方法以环境变量的形式传递给替换进程
            setenv("METHOD", (char *)method.c_str(), 1);

            // 对于GET请求的参数使用环境变量导入，不使用管道，提高效率（参数较少）
            if (method == "GET")
            {
                // putenv 不会复制传递给它的字符串，而是直接存储指针。这意味着您需要确保字符串在程序的整个生命周期内都是有效的。
                // putenv((char *)query_env.c_str()); // 这里有个bug
                setenv("QUERY_STRING", (char *)query_string.c_str(), 1);
                LOG(INFO, "GET Method, Add Query_String env");
            }
            else if (method == "POST")
            {
                // std::string content_length_env = "CONTENT_LENGTH=";
                // content_length_env += std::to_string(content_len);
                // putenv((char *)content_length_env.c_str());
                setenv("CONTENT_LENGTH", (char *)std::to_string(content_len).c_str(), 1);
                LOG(INFO, "POST Method, Add Content_Length env");
            }
            else
            {
                // Do nothing
            }

            // 替换后的进程无法得知管道的读写端，需要重定向
            dup2(output[0], 0);
            dup2(input[1], 1);

            
            // 将子进程替换为对应的CGI程序
            execl(bin.c_str(), bin.c_str(), nullptr);
            exit(1); // 替换失败
        }
        else if (pid > 0)
        {
            // 父进程
            close(input[1]);
            close(output[0]);

            if (method == "POST")
            {
                // 通过管道传递参数
                int total = 0;
                int size = 0; // 调用一次write写入的字节数
                const char *start = request_body.c_str();
                while (total < content_len && (size = write(output[1], start + total, request_body.size() - total)) > 0)
                {
                    total += size;
                }
            }

            // 读取CGI处理后的结果
            LOG(DEBUG, "父进程读取CGI处理后的结果");
            char ch = 0;
            while (read(input[0], &ch, 1) > 0)
            {
                response_body.push_back(ch);
            } // 不会一直读，当另一端关闭后会继续执行下面的代码
            // 等待子进程退出，获取执行状态

            int status;
            pid_t ret = waitpid(pid, &status, 0);
            if (ret == pid)
            {
                // 等待成功
                if (WIFEXITED(status)) // 是否是正常退出
                {
                    // 正常退出
                    if (WEXITSTATUS(status) == 0) // 结果是否正确, 子进程执行结果正确时 返回0
                    {
                        LOG(INFO, "CGI program exits normally with correct results");
                        code = OK;
                    }
                    else
                    {
                        LOG(INFO, "CGI program exits normally with incorrect results");
                        code = BAD_REQUEST;
                    }
                }
                else
                {
                    LOG(INFO, "CGI program exits abnormally");
                    code = INTERNAL_SERVER_ERROR;
                }
            }

            // 使用完毕，需要关闭描述符
            close(input[0]);
            close(output[1]);
            return code;
        }
        else // 创建子进程失败，则返回对应的错误码
        {
            LOG(ERROR, "fork error!");
            code = INTERNAL_SERVER_ERROR;
            return code;
        }
    }
    // 非CGI模式处理, 返回状态码
    int ProcessNonCGI()
    {
        // 因为这里直接获取的是服务器的资源，将资源从磁盘读取到内核后，后续构建响应时直接发送即可
        // 这里直接打开文件，获取文件描述符即可
        _http_response._fd = open(_http_request._path.c_str(), O_RDONLY);
        if (_http_response._fd >= 0) // 文件打开成功
        {
            return OK;
        }
        return INTERNAL_SERVER_ERROR;
    }

    // 构建OK响应页面
    void BuildOKResponse()
    {
        // 构建响应报文：1. 类型(后缀)  2. 长度
        std::string content_type = "Content-Type: ";
        content_type += SuffixToDesc(_http_response._suffix);
        content_type += LINE_END;
        _http_response._response_header.push_back(content_type);

        std::string content_len = "Content-Length: ";
        if (_http_request._cgi) // 如果以CGI方式请求，内容保存在响应正文中
        {
            content_len += std::to_string(_http_response._response_body.size());
        }
        else // 如果以非CGI方式请求，内容是静态网页，在发送响应使获取
        {
            content_len += std::to_string(_http_response._size);
        }
        content_len += LINE_END;
        _http_response._response_header.push_back(content_len);
    }

    // 构建非OK响应页面
    void HandlerError(const std::string &page)
    {
        _http_request._cgi = false; // 返回错误静态页面，以非CGI方式返回

        // 打开对应页面文件，以供后续发送
        _http_response._fd = open(page.c_str(), O_RDONLY);
        if (_http_response._fd > 0) // 打开文件成功
        {
            // 构建响应报头
            struct stat st; // 获取返回静态页面文件的属性
            stat(page.c_str(), &st);

            // 错误页面的文件格式是固定的
            std::string content_type = "Content-Type: text/html";
            content_type += LINE_END;
            _http_response._response_header.push_back(content_type);

            std::string content_len = "Content-Length: ";
            content_len += std::to_string(st.st_size);
            content_len += LINE_END;
            _http_response._response_header.push_back(content_len);
            _http_response._size = st.st_size; // 设置响应页面的文件大小
        }
    }

public:
    EndPoint(int sock = -1)
        : _sock(sock),
          _stop(false)
    {
    }

    // 本次处理是否停止
    bool IsStop()
    {
        return _stop;
    }

    // 读取请求
    void RecvHttpRequest()
    {
        // 两次读取成功才能进行后续操作
        if (!RecvHttpRequestLine() && !RecvHttpRequestHeader())
        {
            ParseHttpRequestLine();
            ParseHttpRequestHeader();
            RecvHttpRequestBody();
        }
    }

    // 处理请求
    void HandlerHttpRequest()
    {
        // 这里只能处理两种请求方式：GET和POST
        auto &method = _http_request._method;
        auto &code = _http_response._status_code;

        if (method != "GET" && method != "POST")
        {
            // 服务无法处理这个请求，给出错误码
            LOG(WARNING, "method is not right");
            code = BAD_REQUEST;
            return;
        }

        // 找到对端要访问资源的路径
        if (method == "GET")
        {
            // 如果请求方法为GET，需要解析URI中是否存在参数
            size_t pos = _http_request._uri.find('?');
            if (pos != std::string::npos)
            {
                Util::CutString(_http_request._uri, _http_request._path, _http_request._query_string, "?");
                // GET方法上传了参数，需要执行
                _http_request._cgi = true;
            }
            else
            {
                // URI中没有上传参数时，URI就是访问资源的路径
                _http_request._path = _http_request._uri;
            }
        }
        else if (method == "POST")
        {
            _http_request._path = _http_request._uri;
            _http_request._cgi = true;
        }
        else
        {
            // Do nothing
        }

        // 将URI中访问资源的路径补全：添加Web根目录、首页
        _http_request._path = WEB_ROOT + _http_request._path;
        if (_http_request._path.back() == '/')
            _http_request._path += HOME_PAGE;

        // 获取请求资源的文件属性
        struct stat st;
        if (stat(_http_request._path.c_str(), &st) == 0)
        {
            // 文件属性获取成功，说明该文件存在，请求资源存在
            if (S_ISDIR(st.st_mode))
            {
                // 该资源是一个目录
                _http_request._path += "/";
                _http_request._path += HOME_PAGE;
                stat(_http_request._path.c_str(), &st);
            }
            else if ((st.st_mode & S_IXGRP) || (st.st_mode & S_IXUSR) || (st.st_mode & S_IXOTH))
            {
                // 请求资源是一个可执行文件, 需要使用CGI模式
                _http_request._cgi = true;
            }
            _http_response._size = st.st_size; // 这里需要返回静态网页的大小：.index
        }
        else
        {
            // 文件属性获取失败，说明该文件存在，请求资源不存在
            LOG(WARNING, "%s NOT_FOUND", _http_request._path.c_str());
            code = NOT_FOUND;
            return;
        }

        //
        // 求出要访问资源路径的后缀（在构建响应信息要用到）
        size_t pos = _http_request._path.find('.');
        if (pos == std::string::npos)
        {
            _http_response._suffix = ".html";
        }
        else
        {
            _http_response._suffix = _http_request._path.substr(pos);
        }

        // 处理请求：分为CGI处理和非CGI处理
        if (_http_request._cgi)
        {
            // 需要使用CGI模式
            LOG(DEBUG, "进行CGI处理");
            code = ProcessCGI(); // 执行后的响应结果被存放在响应正文中
        }
        else
        {
            LOG(DEBUG, "进行非CGI处理");
            code = ProcessNonCGI(); // 简单网页返回，返回静态网页，其大小在打开资源时就已经获取成功
        }
    }

    // 构建响应
    void BuildHttpResponse()
    {
        int code = _http_response._status_code;
        // 构建状态行
        auto &status_line = _http_response._status_line;
        status_line += HTTP_VERSION;
        status_line += " ";
        status_line += std::to_string(code);
        status_line += " ";
        status_line += CodeToDesc(code); // 状态描述
        status_line += LINE_END;

        // 根据状态码构建响应报头
        std::string path = WEB_ROOT;
        path += '/';
        switch (code)
        {
        case OK:
            BuildOKResponse();
            break;
        case NOT_FOUND:
            path += PAGE_404;
            HandlerError(path);
            break;
        case BAD_REQUEST:
            path += PAGE_400;
            HandlerError(path);
            break;
        case INTERNAL_SERVER_ERROR:
            path += PAGE_500;
            HandlerError(path);
            break;
        default:
            break;
        }
    }

    // 发送响应
    bool SendHttpResponse()
    {
        // 1. 发送状态行
        if (::send(_sock, _http_response._status_line.c_str(), _http_response._status_line.size(), 0) <= 0)
        {
            _stop = true;
        }

        // 2. 发送报头
        if (!_stop)
        {
            for (auto &header : _http_response._response_header)
            {
                if (::send(_sock, header.c_str(), header.size(), 0) <= 0)
                {
                    _stop = true;
                    break;
                }
            }
        }

        // 发送空行
        if (!_stop)
        {
            if (send(_sock, _http_response._blank.c_str(), _http_response._blank.size(), 0) <= 0)
            {
                _stop = true; //  发送失败，设置_stop
            }
        }

        // 3. 发送响应正文
        if (!_stop)
        {
            if (_http_request._cgi)
            {

                // 如果是CGI模式，内容就在响应报文中
                auto &response_body = _http_response._response_body;
                const char *start = response_body.c_str();
                int total = 0;
                int size = 0;
                while (total < response_body.size() && (size = ::send(_sock, start + total, response_body.size() - total, 0)) > 0)
                {
                    total += size;
                }
            }
            else
            {
                int n = 0;
                // 如果不是CGI模式，内容文件已经被打开

                // _sock网络通信的套接字，用来发送给客户端
                // _http_response._fd 打开服务器文件资源的文件描述符
                if ((n = sendfile(_sock, _http_response._fd, nullptr, _http_response._size)) <= 0)
                {
                    _stop = true; // 发送失败，设置_stop
                }

                LOG(DEBUG, "_sock:%d", _sock);
                close(_http_response._fd);
            }
        }
        return _stop;
    }
};

// CGI模式处理的函数类型
class CallBack
{
public:
    CallBack()
    {
    }
    void operator()(int sock)
    {
        HandlerRequest(sock);
    }
    void HandlerRequest(int sock)
    {
        LOG(INFO, "handler request begin");

#ifdef DEBUG
        char buffer[4096];
        recv(sock, buffer, sizeof(buffer), 0);
        std::cout << "------------------begin------------------" << std::endl;
        std::cout << buffer << std::endl;
        std::cout << "-------------------end-------------------" << std::endl;
#else
        LOG(DEBUG, "_sock:%d", sock);
        EndPoint *ep = new EndPoint(sock); // 服务器的sock
        LOG(DEBUG, "创建一个EndPoint");
        ep->RecvHttpRequest(); // 读取请求
        if (!ep->IsStop())
        {
            LOG(INFO, "Recv No Error, Begin Handler Request");
            ep->HandlerHttpRequest(); // 处理请求
            ep->BuildHttpResponse();  // 构建响应
            ep->SendHttpResponse();   // 发送响应
            if (ep->IsStop())
            {
                LOG(WARNING, "Send Error, Stop Send Response");
            }
        }
        else
        {
            LOG(WARNING, "Recv Error, Stop Handler Request");
        }
        close(sock); // 关闭与该客户端建立的套接字
        delete ep;
#endif
        LOG(INFO, "handler request end");
    }
    ~CallBack()
    {
    }
};
